#if defined(_DEBUG)
#pragma comment(linker, "/SUBSYSTEM:Console")
#else
#pragma comment(linker, "/SUBSYSTEM:Windows")
#endif

#include "Swarm.h" // Include the Game Class.

#pragma comment (lib, "Mpr.lib")

#define CLIENT_WIDTH 1280
#define CLIENT_HEIGHT 800

// Forward Declarations:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void OnPaint(HWND hWnd);
void OnLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
void OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam);

// Entry point function for the game:
unsigned int WINAPI GameMain(void* lpvArgList);

// The entry point function for Win32 Programs.
int CALLBACK _tWinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine,
  int nCmdShow
)
{
	// Initialize the COM Library.
	CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

	// Registering the Window Class.
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(wcex);
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); //GetStockObject()
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hInstance = hInstance;
	wcex.lpfnWndProc = WindowProc; // Define the message handler function.
	wcex.lpszMenuName = 0;
	wcex.lpszClassName = _T("D2D Template"); // Identify the Window Class.

	wcex.style = CS_HREDRAW | CS_VREDRAW;

	if ( ! RegisterClassEx( &wcex ) )
	{
		// For more error info use: @err, hr
		int response = MessageBox(HWND_DESKTOP, _T("ERROR: Failed to register the window class."),
			_T("Window Registration Failed"), MB_OK | MB_ICONERROR);
		return -1;
	}

	// Calculate the exact window size that can contain the desired client:
	RECT rDesiredClient;
	rDesiredClient.left = 0;
	rDesiredClient.top = 0;
	rDesiredClient.right = CLIENT_WIDTH;
	rDesiredClient.bottom = CLIENT_HEIGHT;
	AdjustWindowRectEx(&rDesiredClient, WS_OVERLAPPEDWINDOW, TRUE, 0);

	// Create a main window large enough to contain the desired client
	//  and positioned in the center on the screen:
	int iWindowWidth = rDesiredClient.right - rDesiredClient.left;
	int iWindowHeight = rDesiredClient.bottom - rDesiredClient.top;
	HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, _T("D2D Template"),WS_OVERLAPPEDWINDOW,
		(GetSystemMetrics(SM_CXSCREEN)/2) - (iWindowWidth/2),
		(GetSystemMetrics(SM_CYSCREEN)/2) - (iWindowHeight/2),
		iWindowWidth, iWindowHeight,
		NULL, NULL, hInstance, NULL);

	if ( ! hWnd)
	{
		int response = MessageBox(HWND_DESKTOP, _T("ERROR: Failed to create the main window."),
			_T("ERROR"), MB_OK | MB_ICONERROR);
		return -2;
	}
	
	Swarm swarm;

	SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG)&swarm);

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	swarm.hGameThread = (HANDLE)_beginthreadex(NULL, 0,
		GameMain, (void*)hWnd, 0, NULL);
#ifdef _DEBUG
	Sleep(1);
#endif

	MSG msg = {};
	while ( GetMessage(&msg, NULL, 0, 0) )
	{
		TranslateMessage( &msg );
		DispatchMessage( &msg );
	}

	CloseHandle(swarm.hGameThread);
	CoUninitialize();

	return 0;
}

int main()
{
	_tWinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWDEFAULT);
	return 0;
}


// Entry point function for the game:
unsigned int WINAPI GameMain(void* lpvArgList)
{
	// Initialize the COM Library.
	CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);

	// Increase the accuracy/precision of the Windows timer:
	TIMECAPS tc;
	timeGetDevCaps(&tc, sizeof(TIMECAPS));
	timeBeginPeriod(tc.wPeriodMin);

	HWND hWnd = (HWND)lpvArgList;
	Swarm *swarm = (Swarm*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

	swarm->SetHwnd(hWnd);
	swarm->Startup();

	SetCursorPos((GetSystemMetrics(SM_CXSCREEN)/2),(GetSystemMetrics(SM_CYSCREEN)/2));
	
	RECT rect;
	GetClientRect(hWnd, &rect);
	swarm->SetCursorPos((rect.right-rect.left)/2, (rect.bottom-rect.top)/2);

	while (swarm->bRunning)
	{
		swarm->ProcessInput();
		swarm->Update(0);
		swarm->Render();
	}

	swarm->Shutdown();


	// Restore the accuracy/precision of the Windows timer:
	timeEndPeriod(tc.wPeriodMin);

	// Uninitialize the COM Library.
	CoUninitialize();

	return 0;
}


// The Main Message Handler function
LRESULT CALLBACK WindowProc(
  HWND hWnd,
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam
)
{
	switch (uMsg)
	{
	// Handle Window Creation:
	case WM_CREATE:
		{
			RAWINPUTDEVICE Rid[2];
			Rid[0].usUsagePage = 0x01; 
			Rid[0].usUsage = 0x02; 
			Rid[0].dwFlags = RIDEV_DEVNOTIFY;
			Rid[0].hwndTarget = 0;
			Rid[1].usUsagePage = 0x01; 
			Rid[1].usUsage = 0x06; 
			Rid[1].dwFlags = RIDEV_DEVNOTIFY;
			Rid[1].hwndTarget = 0;
			if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE)
			{
				MessageBox(HWND_DESKTOP, _T("ERROR: Failed to register raw input devices."),
					_T("Raw Input Error"), MB_OK | MB_ICONERROR);
				return -1;
			}
		}
		break;

	case WM_ERASEBKGND:
		return TRUE;

	// Handle Rendering:
	case WM_PAINT:
		OnPaint(hWnd);
		break;

	// Handle Left Mouse Button pressed:
	case WM_LBUTTONDOWN:
		OnLButtonDown(hWnd, wParam, lParam);
		break;

	// Handle menu items:
	case WM_COMMAND:
		OnCommand(hWnd, wParam, lParam);
		break;

	case WM_SIZE:
		{
			
		}
		break;


	// Handle keyboard input:
	case WM_INPUT:
		{
			Swarm *swarm = (Swarm*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
			RAWINPUT ri;
			UINT dwSize = sizeof(RAWINPUT);

			GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &ri, &dwSize, sizeof(RAWINPUTHEADER));
			
			if (ri.header.dwType == RIM_TYPEKEYBOARD)
				swarm->RawKeyInput(ri.data.keyboard);
			else if (ri.header.dwType == RIM_TYPEMOUSE) 
				swarm->RawMouseInput(ri.data.mouse);
		}
		break;

	// Handle window activation/deactivation:
	case WM_ACTIVATE:
		{
			if(wParam == WA_INACTIVE)
				ShowCursor(true);
			else
			{
				Swarm *swarm = (Swarm*)GetWindowLongPtr(hWnd, GWLP_USERDATA);

				SetCursorPos((GetSystemMetrics(SM_CXSCREEN)/2),(GetSystemMetrics(SM_CYSCREEN)/2));
				RECT rect;
				GetClientRect(hWnd, &rect);
				swarm->SetCursorPos((rect.right-rect.left)/2, (rect.bottom-rect.top)/2);
				ShowCursor(false);
			}
			/*Core *core = (Core*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
			if (LOWORD(wParam) != WA_INACTIVE)
				GetKeyboardState(core->keys); // Query the state of the keyboard.
			else
				ZeroMemory(core->keys, 256); // Clear out the keys buffer.*/
		}
		break;

	case WM_CLOSE:
		{
			Swarm *swarm = (Swarm*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
			swarm->bRunning = FALSE;
			DWORD retval = WaitForSingleObject(swarm->hGameThread, INFINITE);
			DestroyWindow(hWnd);
		}
		break;

	// Handle Window Destruction:
	case WM_DESTROY:
		
		PostQuitMessage(0);
		break;

	// Default message processing:
	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	return 0;
}

void OnPaint(HWND hWnd)
{
	// Without GDI, the programmer must Validate the window.
    ValidateRect(hWnd, NULL);
}


void OnLButtonDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	// Get the mouse click position:
	POINT ptMouse;
	ptMouse.x = LOWORD(lParam);
	ptMouse.y = HIWORD(lParam);

	// Request a Paint Message.
	InvalidateRect(hWnd, NULL, TRUE);
}


void OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	/*int menuid = LOWORD(wParam);
	switch (menuid)
	{
	default:
	break;
	}*/
}